package com.aglhz.nature.utils;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.util.StringTokenizer;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.util.Log;

/**
 * 我的文件操作者，使用该类需要添加以下权限： <br/>
 * <p>
 * android.permission.WRITE_EXTERNAL_STORAGE <br/>
 * </p>
 * 
 * @author Bassy Wain
 * @version 2 该版本取消了Debug标识，论如何都会有Log输出
 * @version 3 增加了{@link #getFileMD5(File)}方法，用于获取文件的MD5值
 * @version 4 增加了{@link #appendAsString(String, String)} 方法，用于追加内容到指定文件
 * @version 5 修复了{@link #deleteAllFile()}方法无法递归删除的问题
 * @version 6 修复了{@link #deleteAllFile()}方法无法递归删除的问题
 * @version 7 修复了{@link #isFileExist(String)}
 *          如果是目录的话，即使存在也返回false。修复了saveAs*方法，文件名为空的问题
 * @version 8 修复了{@link #readAsObject(String)} 返回Boolean对象的问题
 */
public class FileManager extends Object
{
	/** 调试信息过滤标识符 */
	public static String tagDebugLog = "[FileManager]";

	/** 表示将数据存储到SD卡上 */
	public static int MODE_SDCARD = 0;

	/** 表示将数据存储到应用程序私有目录下 */
	public static int MODE_MOBILE = 1;

	/** Context对象 */
	private Context mContext;

	/** 当前工作路径 */
	private String mWorkPath;

	/** 工作模式 */
	private int mWorkMode;

	/** 根目录，与工作模式WordMode相关 */
	private String mRootPath;

	/**
	 * 构造函数，默认使用SD卡并且输出调试信息
	 * 
	 * @since 1.3
	 * 
	 * @param c
	 *            传入一个Context对象
	 * @param workPath
	 *            工作路径。如果该路径不存在，将会自动创建。例如：“com/android/bassy/”。
	 */
	public FileManager(Context c, String workPath)
	{
		this(c, workPath, MODE_SDCARD);
	}

	/**
	 * 构造函数
	 * 
	 * @since 1
	 * 
	 * @param c
	 *            传入一个Context对象
	 * @param workPath
	 *            工作路径。如果该路径不存在，将会自动创建。例如：“com/android/bassy/”。
	 * @param workMode
	 *            工作模式，指示存储介质，可选SD卡（MODE_SDCARD）、手机卡（MODE_MOBILE）。默认为手机卡。
	 */
	public FileManager(Context c, String workPath, int workMode)
	{
		mContext = c;
		mWorkMode = workMode;
		mWorkPath = workPath;

		// 加上路径分割符
		if (!mWorkPath.endsWith(File.separator))
			mWorkPath += File.separator;

		if (mWorkMode == MODE_SDCARD)
			// SD卡
			mRootPath = getSDCardRoot();
		else
			// 手机卡
			mRootPath = getPrivateRoot();

		createDir(mRootPath + mWorkPath);
	}

	/**
	 * 获取当前工作路径 版本1.1 修改了返回的工作路径为绝对路径
	 * 
	 * @since 1
	 * 
	 * @return 工作路径
	 */
	public String getWorkPath()
	{
		return mRootPath + mWorkPath;
	}

	/**
	 * 设置当前工作路径
	 * 
	 * @since 1
	 * 
	 * @param 工作路径
	 */
	public void setWorkPath(String workPath)
	{
		this.mWorkPath = workPath;

		// 加上路径分割符
		if (!mWorkPath.endsWith(File.separator))
			mWorkPath += File.separator;

		createDir(mRootPath + mWorkPath);
	}

	/**
	 * 获取当前工作模式
	 * 
	 * @since 1
	 * 
	 * @return 工作模式
	 */
	public int getWorkMode()
	{
		return mWorkMode;
	}

	/**
	 * 设置当前工作模式
	 * 
	 * @since 1
	 * 
	 * @param 工作模式
	 */
	public void setWorkMode(int workMode)
	{
		if (this.mWorkMode == workMode)
			return;

		this.mWorkMode = workMode;

		if (mWorkMode == MODE_SDCARD)
			// SD卡
			mRootPath = getSDCardRoot();
		else
			// 手机卡
			mRootPath = getPrivateRoot();

		createDir(mRootPath + mWorkPath);
	}

	/**
	 * 判断SD卡是否可用
	 * 
	 * @since 1.3 修改为类方法
	 * 
	 * @return 如果SD卡可用则返回true, 否则返回false;
	 */
	public static boolean isSDCardExist()
	{
		String sDStateString = Environment.getExternalStorageState();

		if (sDStateString.equals(Environment.MEDIA_MOUNTED))
		{
			Log.i(tagDebugLog, "SD卡可用！");
			return true;
		}
		else
		{
			Log.e(tagDebugLog, "SD卡不可用！");
			return false;
		}
	}

	/**
	 * 获取SDCard的根目录
	 * 
	 * @since 1
	 * 
	 *        return 如果SDCard不存在则返回null, 否则返回一个根目录，如"/SDCard/"
	 */
	private String getSDCardRoot()
	{
		if (isSDCardExist() == false)
			return null;

		return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator;
	}

	/**
	 * 获得应用程序私有根目录
	 * 
	 * @since 1
	 * 
	 * @return 返回一个根目录，如"/data/data/com.android.bassy/"
	 */
	private String getPrivateRoot()
	{
		return mContext.getFilesDir().getAbsolutePath() + File.separator;
	}

	/**
	 * 创建目录， 可以创建多级目录
	 * 
	 * @since 1
	 */
	private void createDir(String path)
	{
		Log.i(tagDebugLog, "创建目录：" + path);
		StringTokenizer st = new StringTokenizer(path, "/");
		String path1 = st.nextToken() + "/";
		String path2 = path1;
		while (st.hasMoreTokens())
		{
			path1 = st.nextToken() + "/";
			path2 += path1;
			File inbox = new File(path2);
			if (!inbox.exists())
				inbox.mkdir();
		}

	}

	/**
	 * 获取当前目录下的文件数量
	 * 
	 * @since 1
	 * 
	 * @return 文件数量，获取失败返回-1
	 */
	public int getFileNum()
	{
		File file = new File(mRootPath + mWorkPath);
		if (file.isDirectory())
		{
			Log.i(tagDebugLog, "当前目录下的文件数量为：" + file.list().length);
			return file.list().length;
		}
		else
		{
			Log.e(tagDebugLog, "获取当前目录下的文件数量时出错！");
			return -1;
		}
	}

	/**
	 * 获取指定目录的大小
	 * 
	 * @since 1.3
	 * 
	 * @param folder
	 *            一个文件对象
	 * @return 返回目录大小，如果出错则返回0;
	 */
	public static long getFileSize(File folder)
	{
		try
		{
			long folderSize = 0;

			if (!folder.isDirectory())
				return folder.length();

			File[] filelist = folder.listFiles();

			for (int i = 0; i < filelist.length; i++)
				folderSize = folderSize + getFileSize(filelist[i]);

			return folderSize;
		}
		catch (Exception e)
		{
			return 0;
		}
	}

	/**
	 * 返回一个指向目标位置的File对象
	 * 
	 * @since 1
	 * 
	 * @param fileName
	 *            文件名
	 * @return
	 */
	public File getFile(String fileName)
	{
		return new File(mRootPath + mWorkPath + fileName);
	}

	/**
	 * 判断文件是否存在，如果是目录的话，即使存在也返回false
	 * 
	 * @since 1
	 * 
	 * @param fileName
	 *            指定文件名称
	 * @return 如果文件存在则返回True,否则返回False.
	 */
	public boolean isFileExist(String fileName)
	{
		File file = new File(mRootPath + mWorkPath + fileName);
		boolean exists = file.isFile() && file.exists();
		if (exists)
		{
			Log.i(tagDebugLog, "文件：" + fileName + "存在！");
			return true;
		}
		else
		{
			Log.e(tagDebugLog, "文件：" + fileName + "不存在！");
			return false;
		}
	}

	/**
	 * 删除单个文件
	 * 
	 * @since 1
	 * 
	 * @param fileName
	 *            指定文件名称
	 * @return 返回成功与否，通常，路径错误、文件不存在、文件为目录均为导致删除失败。
	 */
	public boolean deleteFile(String fileName)
	{
		File file = new File(mRootPath + mWorkPath + fileName);

		if (file == null || !file.exists() || file.isDirectory())
		{
			Log.e(tagDebugLog, "文件：" + fileName + "删除失败，可能原因：路径错误、文件不存在、文件为目录！");
			return false;
		}

		file.delete();
		Log.i(tagDebugLog, "文件：" + fileName + "删除成功！");

		return true;
	}

	/**
	 * 删除当前路径下的所有文件
	 * 
	 * @since 6
	 * 
	 * @return 返回成功与否
	 */
	public boolean deleteAllFile()
	{
		boolean result = deleteDir(mRootPath + mWorkPath);
		if (result)
			Log.i(tagDebugLog, "删除当前路径下的所有文件成功");
		else
			Log.e(tagDebugLog, "删除当前路径下的所有文件失败");

		return result;
	}

	/**
	 * 删除目录下所有文件
	 * 
	 * @since 6
	 * 
	 * @param dirName
	 *            指定的绝对路径
	 * @return 返回成功与否
	 */
	private boolean deleteDir(String dirName)
	{
		File dir = new File(dirName);

		if (dir == null || !dir.exists())
		{
			return false;
		}
		else if (dir.isFile())
		{
			return dir.delete();
		}

		for (File file : dir.listFiles())
		{
			if (file.isFile())
			{
				file.delete();
			}
			else if (file.isDirectory())
			{
				deleteDir(file.getAbsolutePath());// 递归
			}
		}

		return dir.delete();
	}

	/**
	 * 以输入流的形式读取文件
	 * 
	 * @since 1
	 * 
	 * @param fileName
	 *            文件名
	 * @return 返回输入流，如果操作失败，则返回false
	 */
	public InputStream readAsInputStream(String fileName)
	{
		Log.i(tagDebugLog, "读取输入流：目标文件" + mRootPath + mWorkPath + fileName);

		try
		{
			FileInputStream fis = new FileInputStream(mRootPath + mWorkPath + fileName);
			Log.i(tagDebugLog, "读取输入流：完成");

			return fis;
		}
		catch (Exception e)
		{
			Log.e(tagDebugLog, "读取输入流：异常（" + e.getMessage() + "）");
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 以字符串形式保存文件
	 * 
	 * @since 1
	 * 
	 * @param fileName
	 *            文件名
	 * @param content
	 *            文件内容
	 * 
	 *            return 如果成功则返回true, 否则返回false
	 */
	public boolean saveAsString(String content, String fileName)
	{
		Log.i(tagDebugLog, "保存字符串：目标文件" + mRootPath + mWorkPath + fileName);

		if (fileName == null)
		{
			Log.i(tagDebugLog, "保存字符串：失败！路径不正确！");
			return false;
		}

		try
		{
			Log.i(tagDebugLog, "保存字符串：正在打开文件...");
			FileOutputStream fileOutputStream = new FileOutputStream(mRootPath + mWorkPath
					+ fileName);

			Log.i(tagDebugLog, "保存字符串：正在保存数据...");
			fileOutputStream.write(content.getBytes());

			Log.i(tagDebugLog, "保存字符串：正在关闭输出流...");
			fileOutputStream.close();
		}
		catch (Exception e)
		{
			Log.e(tagDebugLog, "保存字符串：异常（" + e.getMessage() + "）");
			e.printStackTrace();
			return false;
		}

		Log.i(tagDebugLog, "保存字符串：保存成功！");
		return true;
	}

	/**
	 * 追加字符串到指定文件
	 * 
	 * @since 4
	 * 
	 * @param fileName
	 *            文件名
	 * @param content
	 *            文件内容
	 * 
	 *            return 如果成功则返回true, 否则返回false
	 */
	public boolean appendAsString(String content, String fileName)
	{
		Log.i(tagDebugLog, "追加字符串：目标文件" + mRootPath + mWorkPath + fileName);
		try
		{
			Log.i(tagDebugLog, "追加字符串：正在打开文件...");
			FileOutputStream fileOutputStream = new FileOutputStream(mRootPath + mWorkPath
					+ fileName, true);

			Log.i(tagDebugLog, "追加字符串：正在写入数据...");
			fileOutputStream.write(content.getBytes());

			Log.i(tagDebugLog, "追加字符串：正在关闭输出流...");
			fileOutputStream.close();
		}
		catch (Exception e)
		{
			Log.e(tagDebugLog, "追加字符串：异常（" + e.getMessage() + "）");
			e.printStackTrace();
			return false;
		}

		Log.i(tagDebugLog, "追加字符串：追加成功！");
		return true;
	}

	/**
	 * 以字符串形式读取文件
	 * 
	 * @since 1
	 * 
	 * @param fileName
	 *            文件名
	 * 
	 * @return 如果成功则返回读取到的文件内容, 否则返回null
	 */
	public String readAsString(String fileName)
	{
		String result;

		Log.i(tagDebugLog, "读取字符串：目标文件" + mRootPath + mWorkPath + fileName);
		try
		{
			Log.i(tagDebugLog, "读取字符串：正在打开文件...");
			FileInputStream fileInputStream = new FileInputStream(mRootPath + mWorkPath + fileName);

			// 比特数组
			ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
			byte[] buffer = new byte[1024];
			int len = 0;

			Log.i(tagDebugLog, "读取字符串：正在读取数据...");

			while ((len = fileInputStream.read(buffer)) > 0)
			{
				byteArray.write(buffer, 0, len);
			}

			result = byteArray.toString();

			Log.i(tagDebugLog, "读取字符串：正在关闭输入流...");

			fileInputStream.close();
		}
		catch (Exception e)
		{
			Log.e(tagDebugLog, "读取字符串：异常（" + e.getMessage() + "）");
			e.printStackTrace();
			return null;
		}

		if (result != null)
			Log.i(tagDebugLog, "读取字符串：读取成功！");

		return result;
	}

	/**
	 * 以图片形式保存文件(PNG文件格式)
	 * 
	 * @since 1
	 * 
	 * @param fileName
	 *            文件名
	 * @param bitmap
	 *            图片对象
	 * 
	 *            return 如果成功则返回true, 否则返回false
	 */
	public  boolean saveAsBitmap(Bitmap bitmap, String fileName)
	{
		Log.i(tagDebugLog, "保存图片：目标文件" + mRootPath + mWorkPath + fileName);

		if (fileName == null)
		{
			Log.i(tagDebugLog, "保存图片：失败！路径不正确！");
			return false;
		}

		try
		{
			Log.i(tagDebugLog, "保存图片：正在打开文件...");

			FileOutputStream fileOutputStream = new FileOutputStream(mRootPath + mWorkPath
					+ fileName);

			ByteArrayOutputStream baops = new ByteArrayOutputStream();

			bitmap.compress(CompressFormat.PNG, 0, baops);

			Log.i(tagDebugLog, "保存图片：正在保存数据...");

			fileOutputStream.write(baops.toByteArray());

			Log.i(tagDebugLog, "保存图片：正在关闭输出流...");

			fileOutputStream.close();
		}
		catch (Exception e)
		{
			Log.e(tagDebugLog, "保存图片：异常（" + e.getMessage() + "）");
			e.printStackTrace();
			return false;
		}

		Log.i(tagDebugLog, "保存图片：保存成功");
		return true;
	}

	/**
	 * 以图片形式读取文件
	 * 
	 * @since 1
	 * 
	 * @param fileName
	 *            文件名
	 * 
	 * @return 如果成功则返回读取到的Bitmap对象, 否则返回null
	 */
	public Bitmap readAsBitmap(String fileName)
	{
		Log.i(tagDebugLog, "读取图片：目标文件" + mRootPath + mWorkPath + fileName);

		Bitmap result;

		try
		{
			// 获取文件长度
			File file = new File(mRootPath + mWorkPath + fileName);
			long fileLength = file.length();

			Log.i(tagDebugLog, "读取图片：正在打开文件...");
			FileInputStream fileInputStream = new FileInputStream(mRootPath + mWorkPath + fileName);

			// 比特数组
			ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
			byte[] buffer = new byte[(int) fileLength];
			int len = 0;

			Log.i(tagDebugLog, "读取图片：正在读取数据...");

			while ((len = fileInputStream.read(buffer)) > 0)
			{
				byteArray.write(buffer, 0, len);
			}

			// 返回结果
			result = BitmapFactory.decodeByteArray(buffer, 0, buffer.length);

			Log.i(tagDebugLog, "读取图片：正在关闭输入流...");

			fileInputStream.close();
		}
		catch (Exception e)
		{
			Log.e(tagDebugLog, "读取图片：异常（" + e.getMessage() + "）");
			e.printStackTrace();
			return null;
		}

		if (result != null)
			Log.i(tagDebugLog, "读取图片：读取成功！");

		return result;
	}

	/**
	 * 以对象形式保存文件，该对象必须实现Serializable接口，且不能为内部类对象。
	 * 
	 * @since 1
	 * 
	 * @param c
	 *            要保存的对象
	 * @param fileName
	 *            文件名
	 * 
	 * @return 如果保存成功则返回true, 否则 返回 false;
	 */
	public boolean saveAsObject(Object o, String fileName)
	{
		Log.i(tagDebugLog, "保存对象：目标文件" + mRootPath + mWorkPath + fileName);

		if (fileName == null)
		{
			Log.i(tagDebugLog, "保存对象：失败！路径不正确！");
			return false;
		}

		try
		{
			Log.i(tagDebugLog, "保存对象：正在打开文件...");

			// 注意,这里一定要用绝对路径, 不然会出错哦!!
			FileOutputStream fos = new FileOutputStream(mRootPath + mWorkPath + fileName);

			// 获取对象输出流
			ObjectOutputStream oos = new ObjectOutputStream(fos);

			Log.i(tagDebugLog, "保存对象：正在保存数据...");

			oos.writeObject(o);

			Log.i(tagDebugLog, "保存对象：正在关闭流对象...");

			oos.flush();
			oos.close();
		}
		catch (Exception e)
		{
			Log.e(tagDebugLog, "保存对象：异常（Exception：" + e.getMessage() + "）");
			e.printStackTrace();
			return false;
		}

		Log.i(tagDebugLog, "保存对象：保存成功！");
		return true;
	}

	/**
	 * 以对象形式读取文件，可以通过强制类型转换转化为指定的对象。
	 * 
	 * @since 1
	 * 
	 * @param fileName
	 *            文件名
	 * 
	 * @return 如果读取失败则返回null, 否则返回一个object
	 */
	public Object readAsObject(String fileName)
	{
		Log.i(tagDebugLog, "读取对象：目标文件" + mRootPath + mWorkPath + fileName);

		Object result;
		try
		{
			Log.i(tagDebugLog, "读取对象：正在打开文件...");

			FileInputStream fis = new FileInputStream(mRootPath + mWorkPath + fileName);
			ObjectInputStream ois = new ObjectInputStream(fis);

			Log.i(tagDebugLog, "读取对象：正在读取数据...");

			result = ois.readObject();

			Log.i(tagDebugLog, "读取对象：正在关闭流对象...");

			ois.close();
			fis.close();
		}
		catch (Exception e)
		{
			Log.e(tagDebugLog, "保存对象：异常（" + e.getMessage() + "）");
			e.printStackTrace();
			return null;
		}

		if (result != null)
			Log.i(tagDebugLog, "读取对象：读取成功！");

		return result;
	}

	/**
	 * 获取获取文件的MD5值
	 * 
	 * @param file
	 *            文件
	 * @since 3
	 * @return 获取返回MD5值，失败则返回null
	 */

	public String getMD5(String fileName)
	{
		String result = null;
		MessageDigest digest = null;

		Log.i(tagDebugLog, "获取MD5值：目标文件" + mRootPath + mWorkPath + fileName);
		try
		{
			Log.i(tagDebugLog, "获取MD5值：正在打开文件...");
			digest = MessageDigest.getInstance("MD5");
			FileInputStream fileInputStream = new FileInputStream(mRootPath + mWorkPath + fileName);
			byte[] buffer = new byte[1024];
			int len = 0;

			Log.i(tagDebugLog, "获取MD5值：正在计算文件MD5值...");

			while ((len = fileInputStream.read(buffer, 0, 1024)) != -1)
			{
				digest.update(buffer, 0, len);
			}
			fileInputStream.close();

			BigInteger bigInt = new BigInteger(1, digest.digest());
			result = bigInt.toString(16);

			if (result != null)
				Log.i(tagDebugLog, "获取MD5值：计算结果(" + result + ")");
		}
		catch (Exception e)
		{
			Log.e(tagDebugLog, "获取MD5值：异常（" + e.getMessage() + "）");
			e.printStackTrace();
			return null;
		}
		return result;
	}

	/**
	 * 获取获取文件的MD5值
	 * 
	 * @param file
	 *            文件
	 * @since 3
	 * @return 获取返回MD5值，失败则返回null
	 */

	public static String getFileMD5(File file)
	{
		if (!file.isFile())
		{
			return null;
		}
		MessageDigest digest = null;
		FileInputStream in = null;
		byte buffer[] = new byte[1024];
		int len;
		try
		{
			digest = MessageDigest.getInstance("MD5");
			in = new FileInputStream(file);
			while ((len = in.read(buffer, 0, 1024)) != -1)
			{
				digest.update(buffer, 0, len);
			}
			in.close();
		}
		catch (Exception e)
		{
			e.printStackTrace();
			return null;
		}
		BigInteger bigInt = new BigInteger(1, digest.digest());
		return bigInt.toString(16);
	}

	/**
	 * 获取获取文件的MD5值
	 * 
	 * @param file
	 *            文件
	 * @since 3
	 * @return 获取返回MD5值，失败则返回null
	 */

	public static boolean compareMD5(File file1, File file2)
	{
		if (!file1.isFile() || !file2.isFile() || !file1.exists() || file2.exists())
		{
			return false;
		}
		return getFileMD5(file1) == getFileMD5(file2);
	}

}
